package edu.towson.cosc.cosc455.echapp1.markdowncompiler;

import edu.towson.cosc.cosc455.interfaces.SyntaxAnalyzer;

/**
 * In this class, the principle for the syntaxical rules is the same. 
 * Each of them operates by checking whetever tag comes up next in that rule.
 * If the rule fits, then a method called addToken() adds the token to an array list and
 * calls the getNextToken() function. The method isSpaceorNewline() has the practical effect of
 * making sure that when a tag is expected, it doesn't see the whitespace prior to the tag
 * While still preserving the format of the document.
 */
public class MySyntaxAnalyzer implements SyntaxAnalyzer {
	
	private static MyLexicalAnalyzer lexer = new MyLexicalAnalyzer();
	public static final String NEWLINE = System.getProperty("line.separator");
	

	@Override
	/**
	 * @author Eric
	 * @return void
	 * This method implements the bnf for document, by checking the first token, adding if it is valid, 
	 * then calling methods that complete the bnf, and finally checking the last token
	 */
	public void document() throws CompilerException {
		lexer.addTags(); //adds all of the valid tags to a hashset in lexer
		if(lexer.nextCharacter.isEmpty())
			lexer.getCharacter();
		lexer.getNextToken();
		if (!Compiler.token.equalsIgnoreCase("#START DOCUMENT")){
			String message = "A #Start Document annotation was expected when a " + 
					Compiler.token + " was found!";
			throw new CompilerException(message);
		}
		addToken(); //addToken now calls getNextToken, so I don't have to every time
		define();
		head();
		body();
		if (!Compiler.token.equalsIgnoreCase("#END DOCUMENT")){
			String message = "An #End Document annotation was expected when a " + 
					Compiler.token + " was found!";
			throw new CompilerException(message);
		}
		addToken();
			if(lexer.EOF == 0){
				isSpaceorNewline();
				lexer.getNextToken();
			String message = "The end of file was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			return;
		}
	
	
	@Override
	/**
	 * @return void
	 * Calls a method based on the tag it finds next, then calls itself.
	 */
	public void body() throws CompilerException{
		if(Compiler.token.equalsIgnoreCase("#START BOLD")){
			addToken(); //add #START BOLD, get next token
			bold();
			body();
		}
		else if(Compiler.token.equalsIgnoreCase("#START PARAGRAPH")){
			addToken(); //adding "#START PARAGRAPH" and getting next token
			paragraph();
			body();
		}
		else if(Compiler.token.equalsIgnoreCase("#START ITALICS")){
			addToken();
			italics();
			body();
		}
		else if(Compiler.token.equalsIgnoreCase("#START LIST")){
			addToken();
			list();
			body();
		}
		else if(Compiler.token.equalsIgnoreCase("#START DEFINE")|| Compiler.token.equalsIgnoreCase("#START VIDEO") 
				|| Compiler.token.equalsIgnoreCase("#START AUDIO") || Compiler.token.equalsIgnoreCase("#NEWLINE") || 
					Compiler.token.equalsIgnoreCase("#START USE") || !(Compiler.token.startsWith("#"))){
			innerText();
			body();
		}
		else
			return;
	}
		

	@Override
	/**
	 * @return void
	 * The head method will instantly return if it doesn't find its start tag, otherwise it adds it, then calls the title tag. When it returns, it checks for its ending tag
	 */
	public void head() throws CompilerException {
		isSpaceorNewline();
		if(!Compiler.token.equalsIgnoreCase("#START HEAD")){ //if there is no start head then this is likely null
			return;
		}
			else{
				addToken();
				title(); 
				isSpaceorNewline();
					if (!Compiler.token.equalsIgnoreCase("#END HEAD")){
						String message = "An #End Head annotation was expected when a " + 
								Compiler.token + " was found!";
						throw new CompilerException(message);
					}
					else{
						addToken();
						return;
					}
				}
			}

	@Override
	/**
	 * @return void
	 * As with the others, this checks the bnf for title. However, the loop is a little more complex because the title can be blank
	 */
	public void title() throws CompilerException {
		isSpaceorNewline();
		if(!Compiler.token.equalsIgnoreCase("#START TITLE")) //LIKELY NULL
			return;
		addToken(); //adds #START TITLE, gets next token
		if(!Compiler.token.startsWith("#")){
			addToken(); //addToken also gets next token
			while(!Compiler.token.startsWith("#"))
				addToken();
		
				if (!Compiler.token.equalsIgnoreCase("#END TITLE")){
					String message = "An #End Title annotation was expected when a " + 
						Compiler.token + " was found!";
					throw new CompilerException(message);
			}
			else{
				addToken(); //this also gets the next token which is returned to head();, so getnexttoken(); isn't necessary upon return
				return;
			}
		}
		else{ //this is for if we have no text between the two tags
			if (!Compiler.token.equalsIgnoreCase("#END TITLE")){
				String message = "An #End Title annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			else{
				addToken();
				return;
			}
		}
		}
		

	@Override
	public void paragraph() throws CompilerException {
		isSpaceorNewline();
		define();
		innerParagraph();
		if (!Compiler.token.equalsIgnoreCase("#END PARAGRAPH")){
			String message = "An #End Paragraph annotation was expected when a " + 
					Compiler.token + " was found!";
			throw new CompilerException(message);
		}
		else{
			addToken();
			return;
		}
	}

	@Override
	public void bold() throws CompilerException {
		isSpaceorNewline();
		define();
		innerText();
		if (!Compiler.token.equalsIgnoreCase("#END BOLD")){
			String message = "An #End Bold annotation was expected when a " + 
					Compiler.token + " was found!";
			throw new CompilerException(message);
		}
		else{
			addToken();
			return;
		}

	}

	@Override
	public void italics() throws CompilerException {
		isSpaceorNewline();
		define();
		innerText();
		if (!Compiler.token.equalsIgnoreCase("#END ITALICS")){
			String message = "An #End Italics annotation was expected when a " + 
					Compiler.token + " was found!";
			throw new CompilerException(message);
		}
		else{
			addToken();
			return;
		}

	}

	@Override
	public void list() throws CompilerException {
		isSpaceorNewline();
		if (!Compiler.token.equalsIgnoreCase("#START ITEM")){
			String message = "A #Start Item annotation was expected when a " + 
					Compiler.token + " was found!";
			throw new CompilerException(message);
		}
		else{
			addToken();
			isSpaceorNewline();
			define();
			innerList();
				if (!Compiler.token.equalsIgnoreCase("#END ITEM")){
					String message = "An #End Item annotation was expected when a " + 
							Compiler.token + " was found!";
					throw new CompilerException(message);
				}
				addToken(); //add #END ITEM
				isSpaceorNewline();
				item();
				if (!Compiler.token.equalsIgnoreCase("#END LIST")){
					String message = "An #End List annotation was expected when a " + 
							Compiler.token + " was found!";
					throw new CompilerException(message);
				}
				addToken(); //add #END LIST
				return;
		}

	}

	@Override
	public void item() throws CompilerException {
		isSpaceorNewline();
		if(!Compiler.token.equalsIgnoreCase("#START ITEM")){
			return; //Assume the list is over, that is, no more items
		}
		else{
			addToken();
			define();
			innerList();
			if (!Compiler.token.equalsIgnoreCase("#END ITEM")){
				String message = "An #End Item annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			addToken();
			item();
		}
	}

	@Override
	public void audio() throws CompilerException {
		if(Compiler.token.startsWith("#")){
			String message = "An audio declaration must contain the address of the audio as text. There is no text here, a " +
					Compiler.token + " was found!";
			throw new CompilerException(message);
		}
		else{
			addToken();
			isSpaceorNewline();
			if (!Compiler.token.equalsIgnoreCase("#END AUDIO")){
				String message = "An #End Audio annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			addToken();
			return;
		}

	}

	@Override
	public void video() throws CompilerException {
		if(Compiler.token.startsWith("#")){
			String message = "A video declaration must contain the address of the video as text. There is no text here, a " +
					Compiler.token + " was found!";
			throw new CompilerException(message);
		}
		else{
			addToken();
			isSpaceorNewline();
			if (!Compiler.token.equalsIgnoreCase("#END VIDEO")){
				String message = "An #End Video annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			addToken();
			return;
		}

	}

	@Override
	public void define() throws CompilerException {
		isSpaceorNewline();
		if(!Compiler.token.equalsIgnoreCase("#START DEFINE")){
			return;
		}
			else{
				addToken();
				isSpaceorNewline();
		if(!Compiler.token.equalsIgnoreCase("#NAME")){
			String message = "A #Name annotation was expected when a " + 
					Compiler.token + " was found!";
			throw new CompilerException(message);
		}
		else{
			addToken();
			if(Compiler.token.startsWith("#")){
				String message = "A #Name annotation must be followed by the name of the variable you are defining but a " +
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			else{
				addToken();
				if(!Compiler.token.equalsIgnoreCase("#VALUE")){
					String message = "A #Value annotation was expected when a " + 
							Compiler.token + " was found!";
					throw new CompilerException(message);
				}
				else{
					addToken();
					if(Compiler.token.startsWith("#")){
						String message = "A #Value annotation must be followed by the value of the variable but a " + 
								Compiler.token + " was found!";
						throw new CompilerException(message);
					}
					else{
						addToken();
						if(!Compiler.token.equalsIgnoreCase("#END DEFINE")){
							String message = "An #End Define annotation was expected when a " + 
									Compiler.token + " was found!";
							throw new CompilerException(message);
						}
						else{
							addToken();
							isSpaceorNewline();
							if(Compiler.token.equalsIgnoreCase("#START DEFINE")){
								define();
							}
							else
								return;
						}
					}
				}
			}
		}
	}

	}

	@Override
	public void use() throws CompilerException { //only called if #START USE was found in a valid position
		if(Compiler.token.startsWith("#")){
			String message = "You must specify what variable you wish to use. Instead a " +
					Compiler.token + " was found!";
			throw new CompilerException(message);
		}
		else{
			addToken();
			if(!Compiler.token.equalsIgnoreCase("#END USE")){
				String message = "An #End Use annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
					addToken();
					return;
			}
		}
	
	@Override
	public void addToken() throws CompilerException{
			Compiler.StandardList.add(Compiler.token);
			Compiler.token = "";
			if(lexer.EOF == 0)
			lexer.getNextToken();
	}


	@Override
	public void innerParagraph() throws CompilerException {
		/**@return void
		 * This first looks for a Start define, start audio, start video, or plaintext tag.
		 * If it is found, it runs the first if loop, otherwise, it checks the other loops for the tags mentioned in those.
		 * If none are found it then returns.
		 */
		if(Compiler.token.equalsIgnoreCase("#START DEFINE") || !(Compiler.token.startsWith("#")) 
				|| Compiler.token.equalsIgnoreCase("#START VIDEO") || Compiler.token.equalsIgnoreCase("#START AUDIO")){
			innerText();
			innerParagraph();
		}
		else if(Compiler.token.equalsIgnoreCase("#START BOLD")){
			addToken();
			bold();
			innerParagraph();
		}
		else if(Compiler.token.equalsIgnoreCase("#START ITALICS")){
			addToken();
			italics();
			innerParagraph();
		}
		else if(Compiler.token.equalsIgnoreCase("#START LIST")){
			addToken();
			list();
			innerParagraph();
		}
		else if(Compiler.token.equalsIgnoreCase("#NEWLINE")){
			addToken();
			newline();
			innerParagraph();
		}
		else
			return; //these last two lines of code are just for the sake of being very explicit
	}


	@Override
	public void innerText() throws CompilerException {
		if(!Compiler.token.startsWith("#")){
			addToken();
			innerText();
		}
		else if(Compiler.token.equalsIgnoreCase("#START USE")){
			addToken();
			use();
			innerText();
		}
		else if(Compiler.token.equalsIgnoreCase("#NEWLINE")){
			addToken();
			newline();
			innerText();
		}
		else if(Compiler.token.equalsIgnoreCase("#START AUDIO")){
			addToken();
			audio();
			innerText();
		}
		else if(Compiler.token.equalsIgnoreCase("#START VIDEO")){
			addToken();
			video();
			innerText();
		}
		else
			return;
	}


	@Override
	public void newline() throws CompilerException {
		if(Compiler.token.equalsIgnoreCase("#NEWLINE")){
			addToken();
			newline();
		}
		return;
	}


	@Override
	public void innerList() throws CompilerException {
		if(Compiler.token.equalsIgnoreCase("START BOLD")){
			addToken();
			bold();
			innerList();
		}
		else if(Compiler.token.equalsIgnoreCase("#START ITALICS")){
			addToken();
			italics();
			innerList();
		}
		else if(Compiler.token.equalsIgnoreCase("#START LIST")){
			addToken();
			isSpaceorNewline();
			list();
			innerList();
		}
		else if(Compiler.token.equalsIgnoreCase("#START DEFINE")|| Compiler.token.equalsIgnoreCase("#START VIDEO") 
				|| Compiler.token.equalsIgnoreCase("#START AUDIO") || Compiler.token.equalsIgnoreCase("#NEWLINE") || 
				Compiler.token.equalsIgnoreCase("#START USE") || !(Compiler.token.startsWith("#"))){
			innerText();
			innerList();
		}

		
	}


public void isSpaceorNewline() throws CompilerException{
	if(Compiler.token.startsWith("\\s")|| Compiler.token.contains(NEWLINE) || Compiler.token.contains("\\t") || Compiler.token.startsWith(" ")){
		addToken();
		isSpaceorNewline();
	}
	return;
}
}